import asyncio
import json
from datetime import datetime, timezone

from mitmproxy import ctx
from mitmproxy.options import Options
from mitmproxy.tools.web.master import WebMaster


def format_cookies(cookie_list):
    """将cookies列表转换成字典格式，并确保所有值都是可序列化的。"""
    formatted_cookies = []
    for name, value in cookie_list:
        # 确保name和value都是基本数据类型，这里以str为例
        formatted_cookies.append({
            'name': str(name),
            'value': str(value)
        })
    return formatted_cookies


def name_value(obj):
    """将字典对象转换成名称-值对的列表。"""
    return [{"name": k, "value": v} for k, v in obj.items()]


def calculate_timings(flow):
    """计算和返回请求的各个时间段。"""
    timings_raw = {
        'send': flow.request.timestamp_end - flow.request.timestamp_start,
        'receive': flow.response.timestamp_end - flow.response.timestamp_start,
        'wait': flow.response.timestamp_start - flow.request.timestamp_end,
        'connect': (
                flow.server_conn.timestamp_tcp_setup - flow.server_conn.timestamp_start) if flow.server_conn.timestamp_tcp_setup else -1,
        'ssl': (
                flow.server_conn.timestamp_tls_setup - flow.server_conn.timestamp_tcp_setup) if flow.server_conn.timestamp_tls_setup else -1,
    }
    return {k: int(1000 * v) if v != -1 else -1 for k, v in timings_raw.items()}


def flow_to_har(flow):
    """将flow对象转换成HAR格式的数据。"""
    timings = calculate_timings(flow)
    started_date_time = datetime.fromtimestamp(flow.request.timestamp_start, timezone.utc).isoformat()
    response_body_size = len(flow.response.raw_content) if flow.response.raw_content else 0
    response_body_decoded_size = len(flow.response.content) if flow.response.content else 0
    response_body_compression = response_body_decoded_size - response_body_size

    entry = {
        "startedDateTime": started_date_time,
        "time": sum(v for v in timings.values() if v > -1),
        "request": {
            "method": flow.request.method,
            "url": flow.request.url,
            "httpVersion": flow.request.http_version,
            "cookies": format_cookies(flow.request.cookies.fields),
            "headers": name_value(flow.request.headers),
            "queryString": name_value(flow.request.query or {}),
            "headersSize": len(str(flow.request.headers)),
            "bodySize": len(flow.request.content),
        },
        "response": {
            "status": flow.response.status_code,
            "statusText": flow.response.reason,
            "httpVersion": flow.response.http_version,
            "cookies": format_cookies(flow.response.cookies.fields),
            "headers": name_value(flow.response.headers),
            "content": {
                "size": response_body_size,
                "compression": response_body_compression,
                "mimeType": flow.response.headers.get('Content-Type', '')
            },
            "redirectURL": flow.response.headers.get('Location', ''),
            "headersSize": len(str(flow.response.headers)),
            "bodySize": response_body_size,
        },
        "cache": {},
        "timings": timings,
    }

    entry["response"]["content"]["text"] = flow.response.get_text(strict=False)

    if flow.request.method in ["POST", "PUT", "PATCH"]:
        entry["request"]["postData"] = {
            "mimeType": flow.request.headers.get("Content-Type", ""),
            "text": flow.request.get_text(strict=False),
            "params": name_value(flow.request.urlencoded_form)
        }

    if flow.server_conn.connected:
        entry["serverIPAddress"] = str(flow.server_conn.ip_address[0])

    return {
        "log": {"version": "1.2", "creator": {"name": "mitmproxy har_dump", "version": "0.1", "comment": "mitmproxy"},
                "entries": [entry]}}


class HARConverter:
    @staticmethod
    def request(flow):
        """在请求发出之前进行过滤，避免处理非目标内容。"""
        if flow.request.url.endswith(('.js', '.png', '.jpg', '.css', '.gif')):
            flow.response = None  # 你可以选择直接阻止这类请求，或采取其他措施

    @staticmethod
    def response(flow):
        """处理response事件，并将flow转换为HAR格式数据。只处理返回JSON或HTML的响应。"""
        content_type = flow.response.headers.get("Content-Type", "").lower()

        # 这里我们增加了对 content_type 的检查，确保它符合我们想要的类型
        if "application/json" in content_type or "text/html" in content_type:
            har_data = json.dumps(flow_to_har(flow), ensure_ascii=False)
            ctx.log.info('Flow converted to HAR format')
            ctx.log.info(har_data)
        else:
            ctx.log.info(f'Filtered out flow with Content-Type: {content_type}')


async def start_mitmproxy():
    opts = Options(
        listen_host='0.0.0.0',
        listen_port=8180,
    )
    # opts = Options(listen_host='192.168.114.37', listen_port=8180, web_host='192.168.114.37', web_port=8081)
    opts.add_option("body_size_limit", int, 0, "")

    m = WebMaster(opts, with_termlog=False)
    m.addons.add(HARConverter())

    await m.run()


if __name__ == "__main__":
    asyncio.run(start_mitmproxy())
